import java.util.ArrayList;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Queue;

public class Auxiliar {

	public static int ID=1;

	public static double calcularHeuristica(Estado estado_actual){

		String hoja_nodo_actual = estado_actual.getLugarGeografico().getNombre();
		posicionActual coordenadaUMTactual = UTIL.UMT(hoja_nodo_actual, estado_actual.getLugarGeografico().getX(), estado_actual.getLugarGeografico().getY());

		int xn = coordenadaUMTactual.getX();
		int yn = coordenadaUMTactual.getY();

		return Math.sqrt(Math.pow(launcher.coordenadaUMTfinalX-xn,2)+Math.pow(launcher.coordenadaUMTfinalY-yn,2));
	}

	public static double calcularCosto(Estado estado_actual, Nodo padre){
		double ld = 0;

		if(estado_actual.getAcc() == "N" || estado_actual.getAcc() == "S" || estado_actual.getAcc() == "E" || estado_actual.getAcc() == "O"){
			ld = 25;	
		}else{
			ld = Math.sqrt(2*Math.pow(25,2));
		}

		double incAlt= estado_actual.getAltitud()-padre.getE().getAltitud();
		double longitud= Math.sqrt(Math.pow(ld, 2)+Math.pow(incAlt, 2));

		if(estado_actual.getAcc() == padre.getE().getAcc() || padre.getE().getAcc() == null){
			return longitud;
		}else{
			return longitud*1.10;
		}
	}

	//Dependiendo de la estrategia toma los valores necesarios iniciales
	public static double [] calcularValoracionInicial(String estrategia){

		double [] valor = new double [3];

		switch(estrategia){

		case "profundidad":
			valor[1]=0;		//Heuristica
			valor[2]=0;		//Costo
			valor[0]=0;		//Valoracion

			break;

		case "anchura":
			valor[1]=0;
			valor[2]=0;
			valor[0]=0;

			break;

		case "uniforme":
			valor[1]=0;
			valor[2]=0;
			valor[0]=0;

			break;

		case "voraz":
			valor[1]=calcularHeuristica(launcher.einicial);
			valor[2]=0;
			valor[0]=0;

			break;

		case "A":
			valor[1]=calcularHeuristica(launcher.einicial);
			valor[2]=0;
			valor[0]=valor[1] + valor[2];

			break;

		default:
			System.out.println("La estrategia seleccionada no existe");
			break;


		}

		return valor;
	}
	
	//Dependiendo de la estrategia toma los valores necesarios para sucesores
	public static double calcularValoracion(String estrategia, Nodo n_actual, double costo, double heuristica){
		double valoracion=0;

		switch(estrategia){

		case "profundidad":
			valoracion = (1/(n_actual.getProfundidad()+1));

			break;

		case "anchura":
			valoracion = n_actual.getProfundidad();

			break;

		case "uniforme":
			valoracion = costo;

			break;

		case "voraz":
			valoracion = heuristica;

			break;

		case "A":
			valoracion = costo + heuristica;

			break;

		default:
			System.out.println("La estrategia seleccionada no existe");
			break;

		}
		return valoracion;
	}
	
	//Optimizador de nodos
	public static ArrayList<Nodo> crearListaNodosArbol (ArrayList <Estado> listaSucesores,Nodo n_actual, Hashtable<posicionActual, Nodo> grafoArbol, String estrategia){

		ArrayList<Nodo> listaNodosArbol = new ArrayList<Nodo>();

		for(int i=0;i<listaSucesores.size();i++){
			double costo= (calcularCosto(listaSucesores.get(i), n_actual))+n_actual.getCosto();
			double heuristica = calcularHeuristica(listaSucesores.get(i));
			if(listaSucesores.isEmpty()){
				heuristica= 0;
			}
			double valoracion= calcularValoracion(estrategia, n_actual,costo,heuristica);

			posicionActual Ocurrencia1 = new posicionActual(listaSucesores.get(i).getLugarGeografico().getNombre(), listaSucesores.get(i).getLugarGeografico().getX(), listaSucesores.get(i).getLugarGeografico().getY());
			Nodo n_encontrado= grafoArbol.get(Ocurrencia1);
			if (n_encontrado != null){
				if(n_encontrado.getValoracion() > valoracion){
					grafoArbol.remove(n_encontrado.getE().getLugarGeografico());
					n_encontrado.setCosto(costo);
					n_encontrado.setHeuristica(heuristica);
					n_encontrado.setValoracion(valoracion);
					n_encontrado.setPadre(n_actual);
					n_encontrado.setProfundidad(n_actual.getProfundidad()+1);
					n_encontrado.setE(listaSucesores.get(i));;
					grafoArbol.put(listaSucesores.get(i).getLugarGeografico(), n_encontrado);
				}
			}else{
				Nodo n_insertar= new Nodo(ID++, n_actual, listaSucesores.get(i), n_actual.getProfundidad()+1 , valoracion, heuristica, costo);
				listaNodosArbol.add(n_insertar);
				grafoArbol.put(listaSucesores.get(i).getLugarGeografico(), n_insertar);
			}
		}


		return listaNodosArbol;
	}
	
	// Comparator: ordenadamos la cola de prioridad por valoracion
	public static Comparator<Nodo> idComparator = new Comparator<Nodo>(){

		@Override
		public int compare(Nodo c1, Nodo c2) {
			return (int) (c1.getValoracion() - c2.getValoracion());
		}
	};
	
	//Algoritmo general de busqueda
	public static Nodo busqueda(String estrategia){
		Queue<Nodo> frontera = new PriorityQueue<>(1, idComparator);
		double [] valoresIniciales = calcularValoracionInicial(estrategia);
		double valoracion = valoresIniciales[0];
		double heuristica = valoresIniciales[1];
		double costo = valoresIniciales[2];

		frontera.add(new Nodo(0,null,launcher.einicial,0,valoracion,heuristica,costo));

		Hashtable<posicionActual, Nodo> grafoArbol=new Hashtable<posicionActual,Nodo>();
		posicionActual Ocurrencia1 = new posicionActual(frontera.peek().getE().getLugarGeografico().getNombre(), frontera.peek().getE().getLugarGeografico().getX(), frontera.peek().getE().getLugarGeografico().getY());
		grafoArbol.put(Ocurrencia1, frontera.peek());

		boolean solucion = false;
		Nodo n_actual = null;

		while(!frontera.isEmpty() && !solucion){
			n_actual = frontera.poll();


			if(Problema.Meta(n_actual.getE())){
				System.out.println(n_actual.getProfundidad());
				n_actual.setValoracion(n_actual.getValoracion()-n_actual.getHeuristica()); //Especifico de A* -->testing
				n_actual.setHeuristica(0);												//Especifico de A* -->testing
				solucion = true;
				return n_actual;
			}else{
				if (n_actual.getProfundidad() < launcher.profundidadmax){
					ArrayList<Estado> listaSucesores= Problema.sucesores(n_actual.getE());

					ArrayList<Nodo> listaNodosArbol = crearListaNodosArbol(listaSucesores,n_actual, grafoArbol, estrategia); //En esta se quedarán sólo los nodos que mejoren la valoración actual del nodo
					frontera.addAll(listaNodosArbol);

				}
			}

		}
		return null;
	}
	
}
